iT邦幫忙

2026 iThome 鐵人賽

DAY 4
0
Software Development

系統設計就像九頭蛇:打造社群網站的 30 天系列 第 4

Day 4 REST vs GraphQL:前後端到底該怎麼聊天?

  • 分享至 

  • xImage
  •  

昨天把 Feed 的核心邏輯做出來了,GET /feed 已經能正確回傳 Pikachu 追蹤的人發的貼文,但那時候刻意跳過了一個問題:這支 API 到底該回傳什麼形狀的資料?

目前的做法很直覺,後端把 Post 撈出來,整包塞進 JSON 回傳:

{
  "posts": [
    {
      "id": 101,
      "author_id": 42,
      "content": "Hello PokeThreads",
      "created_at": "2026-01-01T00:00:00Z"
    }
  ],
  "next_cursor": "abc123"
}

這種「一個資源、一個固定形狀」的 API 設計方式,就是 REST(Representational State Transfer)

今天想討論的是:RESTful API 會在哪裡開始卡住?

目前做法:RESTful

REST 的核心概念很單純,把系統拆成一個個「資源(Resource)」,每個資源有自己的網址,再搭配 HTTP 動詞表示要做什麼:

GET    /posts       取得貼文列表
GET    /posts/:id   取得單一貼文
POST   /posts       建立貼文
GET    /feed        取得 Feed
POST   /follow      建立追蹤關係

這正是我們這幾天一路在用的設計方式:資源清楚、動詞固定、每支 API 的輸入輸出都是事先定義好的固定格式。對後端來說非常好懂,也很容易讓 CDN、瀏覽器對 GET 做 HTTP Cache,這點很重要,後面會再提到。

先看看 REST 在 PokeThreads 這種場景下,實際上會遇到什麼狀況。

問題一:Over-fetching(撈多了)

假設手機版的 PokeThreads App,畫面上的 Feed 每則貼文只需要顯示「內容」跟「作者暱稱」兩個欄位,但 GET /feed 回傳的卻是完整的 Post 物件:

{
  "id": 101,
  "author_id": 42,
  "content": "Hello PokeThreads",
  "created_at": "2026-01-01T00:00:00Z",
  "media_url": null,
  "like_count": 12,
  "reply_count": 3
}

前端只用得到 content,其他欄位全部白白傳輸、白白解析,這就是 Over-fetching:拿到的資料比實際需要的還多。

單一一則貼文看起來還好,但 Feed 一次回傳 20 則,乘上大量使用者、大量 Request,多出來的頻寬跟解析成本就不可忽略了。

問題二:Under-fetching(撈不夠)

反過來,如果畫面除了貼文內容,還要顯示作者的頭像跟粉絲數,GET /feed 只回傳 author_id,前端就得再對每個作者額外呼叫一次 GET /users/:id

GET /feed
      ↓
拿到 20 篇貼文,20 個 author_id
      ↓
GET /users/42
GET /users/87
GET /users/103
...(最多可能到 20 次)

這就是 Under-fetching:一支 API 給的資料不夠,前端得再發好幾次 Request 才能拼出畫面要的資料,也就是常聽到的 N+1 Request 問題。

業界常見方法:GraphQL

面對 Over-fetching 跟 Under-fetching,業界一個常見解法是 GraphQL

讓 Client 自己描述「我要什麼形狀的資料」,Server 收到之後只回傳剛好符合的內容,一次搞定。

同樣是拿 Feed 加上作者暱稱的需求,GraphQL 的 Query 大概長這樣:

query {
  feed {
    content
    author {
      nickname
    }
  }
}

Server 回傳:

{
  "data": {
    "feed": [
      { "content": "Hello PokeThreads", "author": { "nickname": "Pikachu" } }
    ]
  }
}

一次 Request,剛好拿到 contentauthor.nickname,沒有多餘欄位,也不需要額外再打一次 GET /users/:id

REST 與 GraphQL 回傳資料形狀比較圖

REST GraphQL
資料形狀 Server 決定,固定 Client 決定,彈性
Over-fetching 容易發生 幾乎不會
Under-fetching / N+1 容易發生 一次 Query 解決
HTTP Cache 天生支援(GET 可被 CDN/瀏覽器快取) 較難直接套用(多半是 POST)
後端複雜度 低,路由對應清楚 較高,需要 Schema、Resolver
常見場景 單一種前端、Public API 多種平台(Web/iOS/Android 需求不同)、大型內部系統

GraphQL 的問題

GraphQL 解決了資料形狀的問題,但也把一部分複雜度從前端搬到了後端:

  • Resolver 也可能踩到 N+1feed { author { nickname } } 如果每篇貼文都各自查一次作者,20 篇貼文一樣會變成 20 次 DB Query,只是問題從「前端打 20 次 API」變成「後端跑 20 次 Query」,通常要靠 Batching(如 DataLoader) 把同一批 Request 合併成一次查詢。
  • Query Cost 難預估:Client 可以自由組合欄位,萬一寫出一個深度巢狀的 Query(貼文 → 作者 → 追蹤者 → 追蹤者的貼文…),單一 Request 可能就讓 Server 做掉大量運算,需要額外設計 Query Complexity 限制。
  • Cache 變難:REST 的 GET /feed 是一個固定網址,瀏覽器、CDN 都能直接快取;GraphQL 通常用同一個 /graphql 端點搭配 POST,沒辦法直接套用這套現成的 HTTP Cache 機制,得自己在應用層另外處理。

小結

回到 PokeThreads 目前的規模:使用者只透過一種 Web 前端,每個畫面需要的資料形狀都算固定,REST 的 Over-fetching / Under-fetching 問題還不明顯,換成 GraphQL 反而是拿簡單問題換複雜方案。

所以這個系列會繼續用 REST 把 PokeThreads 的後端搭起來,維持簡單也保留 HTTP Cache 這個之後很好用的優化空間。但如果之後 PokeThreads 要同時支援 Web、iOS、Android,而且每個平台想要的資料欄位都不太一樣,GraphQL 讓 Client 自己決定資料形狀的彈性,就會變成很值得認真考慮的選項。

這只是前後端溝通方式的其中一層,不管選 REST 還是 GraphQL,最終還是要回到 Server 本身:這幾天我們的 Feed、Post、Follow,全部都靠同一台 Server 在處理。使用者一旦變多,這台 Server 撐不撐得住,才是接下來真正要面對的問題。


上一篇
Day 3 Feed 怎麼產生?
下一篇
Day 5 一台 Server 不夠用:Vertical Scaling vs Horizontal Scaling
系列文
系統設計就像九頭蛇:打造社群網站的 30 天10
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言